/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.springcloudali.ms;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import com.alibaba.csp.sentinel.util.TimeUtil;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;

/**
 * <h1>https://github.com/alibaba/Sentinel/blob/master/sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/flow/FlowThreadDemo.java</h1>
 *
 * @author jialiang.linjl
 */
public class Flow_1_Thread_Demo {

    /** 资源 methodA 所有业务逻辑处理完成的数量 **/
    private static AtomicInteger pass = new AtomicInteger();
    /** 资源 methodA 被拒绝的数量 **/
    private static AtomicInteger block = new AtomicInteger();
    /** 资源 methodA 接收到请求的总数量 **/
    private static AtomicInteger total = new AtomicInteger();
    /** 资源 methodA 真正开始干活时就开始计数，表示 methodA 的任务活跃数 **/
    private static AtomicInteger activeThread = new AtomicInteger();

    private static volatile boolean stop = false;
    private static final int threadCount = 100;

    /** 资源 methodA 总共被发起调用的持续时间 **/
    private static int seconds = 15;
    /** 资源 methodB 处理业务时需要消耗的时间，单位：毫秒 **/
    private static volatile int methodBRunningTime = 2000;

    /**
     * 并发数限流
     * 当并发数大于 5 时则进行阻断
     * 反正一直保持最大活跃任务数为 5
     */
    private static void initFlowRule() {
        List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule rule = new FlowRule();

        // 设置资源名称为：methodA
        rule.setResource("methodA");

        // 设置限流类型：并发数限流
        rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);

        // 设置并发数限流，对应的限制数值
        rule.setCount(5);

        // 设置需要限制哪些应用，如果没有想好需要限制谁的话，那么采用 default 方式
        rule.setLimitApp("default");

        // 将设置好的规则，添加至列表中，并且加载到限流管理器中
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
        System.out.println("Flow_1_Thread rule loaded: " + rules);
    }

    public static void main(String[] args) throws Exception {
        System.out.println(
            "MethodA will call methodB. After running for a while, methodB becomes fast, "
                + "which make methodA also become fast ");
        tick();
        initFlowRule();

        for (int i = 0; i < threadCount; i++) {
            Thread entryThread = new Thread(new ThreadRunTask());
            entryThread.setName("working thread");
            entryThread.start();
        }
    }

    private static void tick() {
        Thread timer = new Thread(new TimerTask());
        timer.setName("sentinel-timer-task");
        timer.start();
    }

    static class TimerTask implements Runnable {

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            System.out.println("begin to statistic!!!");

            long oldTotal = 0;
            long oldPass = 0;
            long oldBlock = 0;

            while (!stop) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                }
                long globalTotal = total.get();
                long oneSecondTotal = globalTotal - oldTotal;
                oldTotal = globalTotal;

                long globalPass = pass.get();
                long oneSecondPass = globalPass - oldPass;
                oldPass = globalPass;

                long globalBlock = block.get();
                long oneSecondBlock = globalBlock - oldBlock;
                oldBlock = globalBlock;

                System.out.print("【秒】倒计时: " + seconds + ", ");
                System.out.println((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS").format(new Date()))
                    + ", 每秒总请求量:" + oneSecondTotal
                    + ", 每秒完成量:" + oneSecondPass
                    + ", 每秒拒绝量:" + oneSecondBlock
                    + ", 每秒活跃量:" + activeThread.get());
                System.out.println();
                if (seconds-- <= 0) {
                    stop = true;
                }
                if (seconds == 5) {
                    System.out.println("method B 原来执行需要花费 2000 毫秒，改造后只需要花费 20 毫秒，系统即将能处理更多的请求。\n");
                    methodBRunningTime = 20;
                }
            }

            long cost = System.currentTimeMillis() - start;
            System.out.println("time cost: " + cost + " ms");
            System.out.println("total:" + total.get() + ", pass:" + pass.get()
                + ", block:" + block.get());
            System.exit(0);
        }
    }

    static class ThreadRunTask implements Runnable {

        @Override
        public void run() {
            while (true) {
                Entry methodA = null;
                try {
                    TimeUnit.MILLISECONDS.sleep(5);
                    methodA = SphU.entry("methodA");
                    activeThread.incrementAndGet();
                    Entry methodB = SphU.entry("methodB");
                    TimeUnit.MILLISECONDS.sleep(methodBRunningTime);
                    methodB.exit();
                    pass.addAndGet(1);
                } catch (BlockException e1) {
                    block.incrementAndGet();
                } catch (Exception e2) {
                    // biz exception
                } finally {
                    total.incrementAndGet();
                    if (methodA != null) {
                        methodA.exit();
                        activeThread.decrementAndGet();
                    }
                }
            }
        }
    }
}
